package com.tianji.remark.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.api.dto.remark.LikedTimesDTO;
import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.utils.BooleanUtils;
import com.tianji.common.utils.ObjectUtils;
import com.tianji.common.utils.StringUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.remark.domain.dto.LikeRecordFormDTO;
import com.tianji.remark.domain.po.LikedRecord;
import com.tianji.remark.mapper.LikedRecordMapper;
import com.tianji.remark.service.ILikedRecordService;
import lombok.RequiredArgsConstructor;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.StringRedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static com.tianji.common.constants.MqConstants.Exchange.LIKE_RECORD_EXCHANGE;
import static com.tianji.common.constants.MqConstants.Key.LIKED_TIMES_KEY_TEMPLATE;
import static com.tianji.remark.constants.RedisContants.LIKES_BIZ_KEY_PREFIX;
import static com.tianji.remark.constants.RedisContants.LIKES_TIME_KEY_PREFIX;

/**
 * <p>
 * 点赞记录表 服务实现类
 * </p>
 *
 * @author fx
 * @since 2024-08-27
 */
@Service
@RequiredArgsConstructor
public class LikedRecordRedisServiceImpl extends ServiceImpl<LikedRecordMapper, LikedRecord> implements ILikedRecordService {

    private final RabbitMqHelper mqHelper;
    private final StringRedisTemplate redisTemplate;

    /**
     * 01 点赞或者取消点赞（day06-随堂-01 优化 ）
     *
     * @param formDTO
     */
    @Override
    public void LikeOrUnlike(LikeRecordFormDTO formDTO) {
        // 0 参数校验
        if (ObjectUtils.isEmpty(formDTO.getBizType())) {
            throw new BadRequestException("业务类型不能为空");
        }
        // 1 判断是 点赞 还是 取消点赞
        Boolean isSuccessed = formDTO.getLiked() ? like(formDTO) : unlike(formDTO);

        // 2 操作失败，结束
        if (BooleanUtils.isFalse(isSuccessed)) return;

        // 3 操作成功，统计点赞数
        Long likeTimes = redisTemplate.opsForSet().size(LIKES_BIZ_KEY_PREFIX + formDTO.getBizId());

        // 4 将点赞数量缓存到 redis 的zset结构中
        if (ObjectUtils.isNotEmpty(likeTimes)) {
            redisTemplate.opsForZSet().add(LIKES_TIME_KEY_PREFIX + formDTO.getBizType(),
                    formDTO.getBizId().toString(), likeTimes);
        }

    }

    /**
     * 02 根据业务id集合查询点赞状态（day06-随堂-02）
     * 查询传入的业务id集合中，当前用户已经点赞的业务id集合
     *
     * @param bizIds
     * @return
     */
    @Override
    public Set<Long> isliked(List<Long> bizIds) {
        // 1 获取当前用户id
        Long userId = UserContext.getUser();

        // 方法一：循环调用ismember实现
        // 2 批量查询点赞转台，将已经点赞的业务id放入集合中
        /*Set<Long> ids = new HashSet<>();
        for (Long bizId : bizIds) {
            Boolean exists = redisTemplate.opsForSet().isMember(LIKES_BIZ_KEY_PREFIX + bizId, userId);
            if (BooleanUtils.isTrue(exists)) {
                ids.add(bizId);
            }
        }
        // 3 返回业务id集合
        return ids;*/

        // 方法二 通过pipeLine管道技术实现
        List pipeResultList = redisTemplate.executePipelined(new RedisCallback<Object>() {

            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                StringRedisConnection connect = (StringRedisConnection) connection;
                for (Long bizId : bizIds) {
                    connect.sIsMember(LIKES_BIZ_KEY_PREFIX + bizId, userId.toString());
                }
                return null;
            }
        });

        // 3 返回业务id集合
        return IntStream.range(0, pipeResultList.size())
                .filter(i -> (Boolean) pipeResultList.get(i))
                .mapToObj(i -> bizIds.get(i))
                .collect(Collectors.toSet());

    }

    /**
     * 03 定时统计点赞次数，并发送消息，用于持久化点赞次数（day06-随堂-03）
     *
     * @param bizType
     * @param maxBizSize
     */
    @Override
    public void readLikedTimesAndSendMessage(String bizType, Integer maxBizSize) {
        // 1 批量读取redis中的点赞次数数据
        Set<ZSetOperations.TypedTuple<String>> typedTuples = redisTemplate.opsForZSet().popMin(LIKES_TIME_KEY_PREFIX + bizType, maxBizSize);
        // 2 数据转换 -> List<LikedTimesDTO>
        while (ObjectUtils.isNotEmpty(typedTuples)) {
            List<LikedTimesDTO> messages = new ArrayList<>();
            for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {
                String bizId = typedTuple.getValue();
                Double likeTimes = typedTuple.getScore();
                if (ObjectUtils.isEmpty(bizId) || ObjectUtils.isEmpty(likeTimes)) {
                    continue;
                }
                messages.add(LikedTimesDTO.of(Long.valueOf(bizId), likeTimes.intValue()));
            }
            if (ObjectUtils.isNotEmpty(messages)){
                // 3 发送mq消息
                mqHelper.send(LIKE_RECORD_EXCHANGE,
                        StringUtils.format(LIKED_TIMES_KEY_TEMPLATE, bizType),
                        messages);
            }
            // 4 继续查询下一轮
            typedTuples = redisTemplate.opsForZSet().popMin(LIKES_TIME_KEY_PREFIX + bizType, maxBizSize);

        }


    }

    /**
     * tool-01 点赞 新增点赞记录
     *
     * @param formDTO
     * @return
     */
    private Boolean like(LikeRecordFormDTO formDTO) {
        // 1 组装大key
        String key = LIKES_BIZ_KEY_PREFIX + formDTO.getBizId();
        // 2 执行set的sadd命令，添加点赞人的id到点赞业务id的set集合中
        Long result = redisTemplate.opsForSet().add(key, UserContext.getUser().toString());
        // 3 返回点赞是否成功
        return ObjectUtils.isNotEmpty(result) && result > 0;
    }

    /**
     * tool-02 取消点赞 删除点赞记录
     *
     * @param formDTO
     * @return
     */
    private Boolean unlike(LikeRecordFormDTO formDTO) {
        // 1 组装大key
        String key = LIKES_BIZ_KEY_PREFIX + formDTO.getBizId();
        // 2 执行set的srem命令，添加点赞人的id到点赞业务id的set集合中
        Long result = redisTemplate.opsForSet().remove(key, UserContext.getUser().toString());
        // 3 返回取消点赞是否成功
        return ObjectUtils.isNotEmpty(result) && result > 0;
    }
}

























